package w.lz.wt;

import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;

/**
 * @author 张少昆
 */
public class WeeksUtil {
    private WeeksUtil() {
    }

    /**
     * 根据规则计算日期的年度周数。
     *
     * @param date                 需要判断的日期
     * @param weekStart            一周从周几开始            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年；是否包含最后一个跨年周（如不包含就是下一年的首周）
     * @return 202344这种周数
     */
    public static int weekNoOfYear(LocalDate date, DayOfWeek weekStart, boolean includeLastCrossWeek) {
        // 判断临界情况
        LocalDate firstWeekStartOfYear = firstWeekStartOfYear(date.getYear(), weekStart, includeLastCrossWeek);
        if (date.isBefore(firstWeekStartOfYear)) {
            // last year last week
            return lastWeekNoOfYear(date.getYear() - 1, weekStart, includeLastCrossWeek);
        }
        // 否则都是本年度的
        LocalDate curStart = date.with(TemporalAdjusters.previousOrSame(weekStart));
        return date.getYear() * 100 + (int) (Duration.between(firstWeekStartOfYear.atStartOfDay(), curStart.atStartOfDay()).toDays() / 7 + 1);
    }

    /**
     * 根据规则计算年度第一周的开始日期。
     *
     * @param year                 年份 2023这种
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @return 年度第一周
     */
    public static LocalDate firstWeekStartOfYear(int year, DayOfWeek weekStart, boolean includeLastCrossWeek) {
        LocalDate firstDayOfYear = LocalDate.of(year, 1, 1);
        return includeLastCrossWeek ? firstDayOfYear.with(TemporalAdjusters.nextOrSame(weekStart)) : firstDayOfYear.with(TemporalAdjusters.previousOrSame(weekStart));
    }

    /**
     * 根据规则计算年度最后一周的开始日期。
     *
     * @param year                 年份 2023这种
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @return
     */
    public static LocalDate lastWeekStartOfYear(int year, DayOfWeek weekStart, boolean includeLastCrossWeek) {
        LocalDate lastDayOfYear = LocalDate.ofYearDay(year + 1, 1).minusDays(1);
        if (includeLastCrossWeek) {
            return lastDayOfYear.with(TemporalAdjusters.previousOrSame(weekStart));
        }
        //
        return lastDayOfYear.with(TemporalAdjusters.previousOrSame(weekStart.minus(1))).with(TemporalAdjusters.previousOrSame(weekStart));
    }

    /**
     * 根据规则计算年度最后一周的结束日期。
     *
     * @param year                 年份 2023这种
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @return
     */
    public static LocalDate lastWeekEndOfYear(int year, DayOfWeek weekStart, boolean includeLastCrossWeek) {
        if (includeLastCrossWeek) {
            return lastWeekStartOfYear(year, weekStart, includeLastCrossWeek).with(TemporalAdjusters.nextOrSame(weekStart.minus(1)));
        }
        return LocalDate.ofYearDay(year + 1, 1).minusDays(1).with(TemporalAdjusters.previousOrSame(weekStart.minus(1)));
    }

    /**
     * 根据规则计算年度最后一周的周数。
     *
     * @param year                 年份 2023这种
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @return
     */
    public static int lastWeekNoOfYear(int year, DayOfWeek weekStart, boolean includeLastCrossWeek) {
        LocalDate d1 = firstWeekStartOfYear(year, weekStart, includeLastCrossWeek);
        LocalDate d2 = lastWeekStartOfYear(year, weekStart, includeLastCrossWeek);
        return year * 100 + (int) (Duration.between(d1.atStartOfDay(), d2.atStartOfDay()).toDays() / 7 + 1);
    }

    /**
     * 根据周数获取一周的起始日期
     *
     * @param weekNo               周数 202344这种
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @return
     */
    public static LocalDate weekStartByWeekNo(int weekNo, DayOfWeek weekStart, boolean includeLastCrossWeek) {
        int year = weekNo / 100;
        int week = weekNo % 100;
        return firstWeekStartOfYear(year, weekStart, includeLastCrossWeek).plusDays((week - 1) * 7L);
    }

    /**
     * 根据周数获取一周的结束日期
     *
     * @param weekNo               周数 202344这种
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @return
     */
    public static LocalDate weekEndByWeekNo(int weekNo, DayOfWeek weekStart, boolean includeLastCrossWeek) {
        int year = weekNo / 100;
        int week = weekNo % 100;
        return firstWeekStartOfYear(year, weekStart, includeLastCrossWeek).plusDays(week * 7L + 6);
    }

    /**
     * 根据规则计算周数对应的月份。
     *
     * @param weekNo               周数 202344这种
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @param weekBelongPolicyEnum 跨月的周属于哪个月
     * @return
     */
    public static YearMonth monthByWeekNo(int weekNo, DayOfWeek weekStart, boolean includeLastCrossWeek, WeekBelongPolicyEnum weekBelongPolicyEnum) {
        LocalDate firstDayOfSpecifiedWeek = weekStartByWeekNo(weekNo, weekStart, includeLastCrossWeek);
        LocalDate lastDayOfSpecifiedWeek = firstDayOfSpecifiedWeek.plusDays(6);
        // 特殊处理跨年周
        boolean crossYear = firstDayOfSpecifiedWeek.getYear() != lastDayOfSpecifiedWeek.getYear();
        boolean firstWeek = weekNo % 100 == 1;// or last week
        if (firstWeek) {
            return YearMonth.of(weekNo / 100, 1);
        } else {
            if (crossYear) {
                if (includeLastCrossWeek) {
                    return YearMonth.of(weekNo / 100, 12);
                } else {
                    return YearMonth.of(weekNo / 100 + 1, 1);
                }
            }
        }
        // 正常情况
        switch (weekBelongPolicyEnum) {
            case N:
                return YearMonth.from(firstDayOfSpecifiedWeek);

            case NPP:
                return YearMonth.from(lastDayOfSpecifiedWeek);
            case MAJOR:
                Month month = firstDayOfSpecifiedWeek.getMonth();
                if (month == lastDayOfSpecifiedWeek.getMonth()) {
                    return YearMonth.from(firstDayOfSpecifiedWeek);
                }
                // 过半就行，不需要全部计算
                long count = IntStream.range(1, 4).boxed().map(i -> firstDayOfSpecifiedWeek.plusDays(i).getMonth()).distinct().count();
                if (count == 1) {
                    return YearMonth.from(firstDayOfSpecifiedWeek);
                }
                return YearMonth.from(lastDayOfSpecifiedWeek);
            default:
                throw new IllegalArgumentException("WeekBelongPolicyEnum null!");
        }
    }

    /**
     * 根据规则计算月份的周数列表。
     *
     * @param yearMonth            年月
     * @param weekStart            一周从周几开始
     * @param includeLastCrossWeek 跨年的周属于N年还是N+1年，true属于N年，false属于N+1年
     * @param weekBelongPolicyEnum 跨月的周属于哪个月
     * @return
     */
    public static List<Integer> weekListOfMonth(YearMonth yearMonth, DayOfWeek weekStart, boolean includeLastCrossWeek, WeekBelongPolicyEnum weekBelongPolicyEnum) {
        LocalDate firstDayOfMonth = yearMonth.atDay(1);
        LocalDate lastDayOfMonth = yearMonth.atEndOfMonth();
        int firstWeekNo = weekNoOfYear(firstDayOfMonth, weekStart, includeLastCrossWeek);
        int lastWeekNo = weekNoOfYear(lastDayOfMonth, weekStart, includeLastCrossWeek);
        YearMonth m1 = monthByWeekNo(firstWeekNo, weekStart, includeLastCrossWeek, weekBelongPolicyEnum);
        YearMonth m2 = monthByWeekNo(lastWeekNo, weekStart, includeLastCrossWeek, weekBelongPolicyEnum);

        ArrayList<Integer> weeks = new ArrayList<>();
        int from;
        int to;

        if (yearMonth.equals(m1)) {
            from = firstWeekNo;
        } else {
            if (yearMonth.getYear() == m1.getYear()) {
                from = firstWeekNo + 1; // CROSS YEAR
            } else {
                from = yearMonth.getYear() * 100 + 1;
            }
        }
        if (yearMonth.equals(m2)) {
            to = lastWeekNo + 1;
        } else {
            to = lastWeekNo;
        }

        while (from < to) {
            weeks.add(from);
            from++;
        }
        return weeks;
    }


    // //-------------每周一开始，跨年的算去年的。 建议使用上面的，更通用。
    //
    // /**
    //  * 每周一开始，跨年的算去年的。
    //  *
    //  * @param date
    //  * @return
    //  */
    // public static int weekNoOfYear(LocalDate date) {
    //     // 1. 判断临界情况
    //     LocalDate firstMondayOfYear = firstMondayOfYear(date.getYear());
    //     if (date.isBefore(firstMondayOfYear)) {
    //         // last year last week
    //         return lastWeekNoOfYear(date.getYear() - 1);
    //     }
    //     // 否则都是本年度的
    //     LocalDate curMonday = date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
    //     return (int) (Duration.between(firstMondayOfYear.atStartOfDay(), curMonday.atStartOfDay()).toDays() / 7 + 1);
    // }
    //
    // public static LocalDate firstMondayOfYear(int year) {
    //     LocalDate firstDayOfYear = LocalDate.of(year, 1, 1);
    //     return firstDayOfYear.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
    // }
    //
    // public static LocalDate lastMondayOfYear(int year) {
    //     LocalDate firstDayOfNextYear = LocalDate.ofYearDay(year + 1, 1);
    //     return firstDayOfNextYear.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
    // }
    //
    // public static LocalDate lastSundayOfYear(int year) {
    //     return lastMondayOfYear(year).with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
    // }
    //
    // public static int lastWeekNoOfYear(int year) {
    //     LocalDate d1 = firstMondayOfYear(year);
    //     LocalDate d2 = lastMondayOfYear(year);
    //     return (int) (Duration.between(d1.atStartOfDay(), d2.atStartOfDay()).toDays() / 7 + 1);
    // }
}